/* Copyright (c) 1995-2000, The Hypersonic SQL Group.
 * All rights reserved.
 *
 * Redistribution and use _in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions _in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer _in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the Hypersonic SQL Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE HYPERSONIC SQL GROUP,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * This software consists of voluntary contributions made by many individuals
 * on behalf of the Hypersonic SQL Group.
 *
 *
 * For work added by the HSQL Development Group:
 *
 * Copyright (c) 2001-2008, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use _in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions _in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer _in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


using System;
using System.Net.Sockets;
using System.IO;
using System.Text;
using System.Threading;
using EffiProzDB.Lib;
using EffiProzDB.RowIO;

// fredt@users 20020215 - patch 461556 by paul-h@userus - server factory
// fredt@users 20020424 - patch 1.7.0 by fredt - shutdown without exit
// fredt@users 20021002 - patch 1.7.1 by fredt - changed notification method
// fredt@users 20030618 - patch 1.7.2 by fredt - changed read/write methods

/**
 *  All ServerConnection objects are listed _in a Set _in server
 *  and removed by this class when closed.<p>
 *
 *  When the database or server is shutdown, the signalClose() method is called
 *  for all current ServerConnection instances. This will call the private
 *  close() method unless the ServerConnection thread itself has caused the
 *  shutdown. In this case, the keepAlive flag is set to false, allowing the
 *  thread to terminate once it has returned the result of the operation to
 *  the client.
 *  (fredt@users)<p>
 *
 * Rewritten _in version HSQLDB 1.7.2, based on original Hypersonic code.
 *
 * @author Thomas Mueller (Hypersonic SQL Group)
 * @author fredt@users
 * @version 1.8.0
 * @since Hypersonic SQL
 */

namespace EffiProzDB
{

    public class ServerConnection
    {

        public bool keepAlive;
        private string user;
        private string password;
        public int dbID;
        private volatile Session session;
        private TcpClient socket;
        private Server server;
        private Stream dataInput;
        private Stream dataOutput;
        private static int mCurrentThread = 0;
        private int mThread;
        public const int BUFFER_SIZE = 0x1000;
        public byte[] mainBuffer = new byte[BUFFER_SIZE];
        public RowOutputBinary rowOut = new RowOutputBinary(BUFFER_SIZE);
        public RowInputBinary rowIn = null;
        public Thread runnerThread;

        /**
         * Creates a new ServerConnection to the specified Server on the
         * specified socket.
         *
         * @param socket the network socket on which Server communication
         *      takes place
         * @param server the Server instance to which the object
         *      represents a connection
         */
        public ServerConnection(TcpClient socket, Server server)
        {

            rowIn =new RowInputBinary(rowOut);
            this.socket = socket;
            this.server = server;

            lock (typeof(ServerConnection))
            {
                mThread = mCurrentThread++;
            }

            lock (server.serverConnSet)
            {
                server.serverConnSet.Add(this);
            }
        }

        /**
         * Signals this object to close, including exiting the thread running
         * the request handling loop
         */
        public void signalClose()
        {
            keepAlive = false;

            if (!Thread.CurrentThread.Equals(runnerThread))
            {
                close();
            }
        }

        /**
         * Closes this connection.
         */
        private void close()
        {

            if (session != null)
            { 
                session.close();

                session = null;
            }

            // fredt@user - closing the socket is to stop this thread
            try
            {
                socket.Close();
            }
            catch (Exception) { }

            socket = null;

            lock (server.serverConnSet)
            {
                server.serverConnSet.Remove(this);
            }
        }

        /**
         * Initializes this connection.
         */
        private void init()
        {

            runnerThread = Thread.CurrentThread;
            keepAlive = true;

            try
            {
                socket.NoDelay = true;
                //socket.SetSocketOption (SocketOptionLevel.Tcp, SocketOptionName.NoDelay,true);

                dataOutput = dataInput = socket.GetStream();//new NetworkStream(socket);
                //dataOutput =new BufferedStream(new NetworkStream(socket));

                Result resultIn = Result.read(rowIn, dataInput);
                Result resultOut;

                try
                {
                    dbID = server.getDBID(resultIn.subSubString);
                    user = resultIn.getMainString();
                    password = resultIn.getSubString();

                    if (!server.isSilent())
                    {
                        server.printWithThread(mThread
                                               + ":trying to connect user "
                                               + user);
                    }

                    session = DatabaseManager.newSession(dbID,
                                                         resultIn.getMainString(),
                                                         resultIn.getSubString());
                    resultOut = new Result(ResultConstants.UPDATECOUNT);
                    resultOut.databaseID = session.getDatabase().databaseID;
                    resultOut.sessionID = session.getId();
                }
                catch (HsqlException e)
                {
                    session = null;
                    resultOut = new Result(e, null);
                }
                catch (Exception e)
                {
                    session = null;
                    resultOut = new Result(e, null);
                }

                Result.write(resultOut, rowOut, dataOutput);

                return;
            }
            catch (Exception)
            {
                server.printWithThread(mThread + ":couldn't connect " + user);
            }

            close();
        }

        /**
         * Initializes this connection and runs the request handling
         * loop until closed.
         */
        public void run()
        {

            init();

            if (session != null)
            {
                try
                {
                    while (keepAlive)
                    {
                        if (!socket.Connected)
                            break;
                        if (socket.Available == 0)
                        {
                            Thread.Sleep(10);
                            continue;
                        }
                        Result resultIn = Result.read(rowIn, dataInput);

                        server.printRequest(mThread, resultIn);

                        Result resultOut;

                        if (resultIn.mode == ResultConstants.HSQLRESETSESSION)
                        {
                            resultOut = resetSession();
                        }
                        else
                        {
                            resultOut = session.execute(resultIn);
                        }

                        Result.write(resultOut, rowOut, dataOutput);
                        rowOut.setBuffer(mainBuffer);
                        rowIn.resetRow(mainBuffer.Length);
                    }
                }
                catch (IOException )
                {

                  //   fredt - is thrown when connection drops
                    server.printWithThread(mThread + ":disconnected " + user);
                }
                catch (HsqlException e)
                {

                   //  fredt - is thrown while constructing the result
                    server.printStackTrace(e);
                }
            }

            close();
        }

        /**
         * Used by pooled connections to close the existing SQL session and open
         * a new one.
         */
        private Result resetSession()
        {

            Result resultOut;

            if (!server.isSilent())
            {
                server.printWithThread(mThread + ":trying to connect user "
                                       + user);
            }

            try
            {
                session.close();

                session = DatabaseManager.newSession(dbID, user, password);
                resultOut = new Result(ResultConstants.UPDATECOUNT);
                resultOut.databaseID = session.getDatabase().databaseID;
                resultOut.sessionID = session.getId();
            }
            catch (HsqlException e)
            {
                session = null;
                resultOut = new Result(e, null);
            }

            return resultOut;
        }

        /**
         * Retrieves the thread name to be used  when
         * this object is the Runnable object of a Thread.
         *
         * @return the thread name to be used  when this object is the Runnable
         * object of a Thread.
         */
        public string getConnectionThreadName()
        {
            return "EffiProzDB Connection @" + string.Format("{0:x2}", this.GetHashCode());
        }
    }
}
